001 /* 002 * Copyright 2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.library.console; 020 021 import java.io.File; 022 import java.io.IOException; 023 import java.net.URI; 024 import java.util.ArrayList; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Hashtable; 028 029 import net.dpml.util.Logger; 030 031 import net.dpml.lang.Part; 032 033 import net.dpml.library.Module; 034 import net.dpml.library.Resource; 035 import net.dpml.library.Builder; 036 import net.dpml.library.Type; 037 import net.dpml.library.info.ResourceDirective.Classifier; 038 import net.dpml.library.info.Scope; 039 import net.dpml.library.impl.DefaultLibrary; 040 041 import net.dpml.cli.Option; 042 import net.dpml.cli.Group; 043 import net.dpml.cli.CommandLine; 044 import net.dpml.cli.commandline.Parser; 045 import net.dpml.cli.util.HelpFormatter; 046 import net.dpml.cli.OptionException; 047 import net.dpml.cli.DisplaySetting; 048 import net.dpml.cli.builder.ArgumentBuilder; 049 import net.dpml.cli.builder.GroupBuilder; 050 import net.dpml.cli.builder.DefaultOptionBuilder; 051 import net.dpml.cli.builder.CommandBuilder; 052 import net.dpml.cli.option.PropertyOption; 053 import net.dpml.cli.validation.URIValidator; 054 055 /** 056 * Plugin that handles multi-project builds based on supplied commandline arguments. 057 * 058 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 059 * @version 1.1.0 060 */ 061 public class BuilderPlugin 062 { 063 // ------------------------------------------------------------------------ 064 // static 065 // ------------------------------------------------------------------------ 066 067 private static final URI ANT_BUILDER_URI; 068 069 // ------------------------------------------------------------------------ 070 // state 071 // ------------------------------------------------------------------------ 072 073 private final Logger m_logger; 074 private final DefaultLibrary m_library; 075 private final Map m_map = new Hashtable(); 076 077 private boolean m_verbose; 078 private boolean m_expand = false; 079 080 // ------------------------------------------------------------------------ 081 // constructors 082 // ------------------------------------------------------------------------ 083 084 /** 085 * Builder establishment. System property have already been assigned 086 * to the current jvm by depot. 087 * 088 * @param logger the assigned logging channel 089 * @param args supplimentary command line arguments 090 * @exception Exception if the build fails 091 */ 092 public BuilderPlugin( Logger logger, String[] args ) 093 throws Exception 094 { 095 m_logger = logger; 096 097 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() ); 098 099 CommandLine line = getCommandLine( args ); 100 m_verbose = line.hasOption( VERBOSE_OPTION ); 101 102 // setup the build version 103 104 if( line.hasOption( VERSION_OPTION ) ) 105 { 106 String version = (String) line.getValue( VERSION_OPTION, "SNAPSHOT" ); 107 System.setProperty( "build.signature", version ); 108 if( m_verbose ) 109 { 110 getLogger().info( "Setting version to: " + version ); 111 } 112 } 113 114 if( line.hasOption( DECIMAL_OPTION ) ) 115 { 116 System.setProperty( Resource.DECIMAL_VERSIONING_KEY, "true" ); 117 if( m_verbose ) 118 { 119 getLogger().info( "Enabling decimal versioning." ); 120 } 121 } 122 123 m_library = new DefaultLibrary( logger ); 124 125 try 126 { 127 if( line.hasOption( HELP_OPTION ) ) 128 { 129 processHelp(); 130 System.exit( 0 ); 131 } 132 else 133 { 134 if( line.hasOption( LIST_OPTION ) ) 135 { 136 m_expand = line.hasOption( EXPAND_OPTION ); 137 Resource[] resources = getTargetSelection( line ); 138 if( resources.length == 0 ) 139 { 140 getLogger().info( "Empty selection." ); 141 System.exit( 0 ); 142 } 143 else 144 { 145 list( resources ); 146 } 147 } 148 else 149 { 150 Resource[] resources = getSelection( line ); 151 if( resources.length == 0 ) 152 { 153 getLogger().info( "Empty selection." ); 154 System.exit( 0 ); 155 } 156 boolean result = process( line, resources ); 157 if( !result ) 158 { 159 System.exit( 1 ); 160 } 161 else 162 { 163 System.exit( 0 ); 164 } 165 } 166 } 167 } 168 catch( OptionException e ) 169 { 170 m_logger.error( e.getMessage() ); 171 } 172 } 173 174 private CommandLine getCommandLine( String[] args ) throws Exception 175 { 176 Parser parser = new Parser(); 177 parser.setGroup( COMMAND_GROUP ); 178 return parser.parse( args ); 179 } 180 181 private Part createPart( URI uri ) throws Exception 182 { 183 try 184 { 185 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() ); 186 return Part.load( uri ); 187 } 188 catch( Exception e ) 189 { 190 final String error = 191 "Unexpected error occured while attempting to load builder.\nURI: " 192 + uri; 193 throw new BuilderError( error, e ); 194 } 195 } 196 197 private Builder createBuilder( Part part ) throws Exception 198 { 199 Object[] params = new Object[]{m_logger, m_library, new Boolean( m_verbose )}; 200 return (Builder) part.instantiate( params ); 201 } 202 203 /** 204 * Resolve the project selection taking into account any overriding -s 205 * selection option, the -c switch, or in the absence of a selction, the 206 * implicit slection relative to the current working directory. 207 * 208 * @param line the commandline 209 * @return the resolved array of resources sorted relative to build sequence 210 */ 211 private Resource[] getSelection( CommandLine line ) throws Exception 212 { 213 ArrayList list = new ArrayList(); 214 Resource[] targets = getTargetSelection( line ); 215 for( int i=0; i<targets.length; i++ ) 216 { 217 Resource project = targets[i]; 218 if( Classifier.LOCAL.equals( project.getClassifier() ) ) 219 { 220 list.add( project ); 221 } 222 } 223 return (Resource[]) list.toArray( new Resource[ list.size() ] ); 224 } 225 226 /** 227 * Get the base selection and check if the consumer switch is present and 228 * if so build the consumers list from the selection list. 229 * 230 * @param line the commandline 231 * @return the array of projects in build order 232 */ 233 private Resource[] getTargetSelection( CommandLine line ) throws Exception 234 { 235 Resource[] resources = getBaseSelection( line ); 236 if( resources.length == 0 ) 237 { 238 return resources; 239 } 240 boolean flag = line.hasOption( CONSUMERS_OPTION ); 241 if( flag ) 242 { 243 if( resources.length != 1 ) 244 { 245 final String error = 246 "Consumer resolution against a multi-element selection is not supported."; 247 getLogger().error( error ); 248 return new Resource[0]; 249 } 250 else 251 { 252 Resource resource = resources[0]; 253 return resource.getConsumers( true, true ); 254 } 255 } 256 else 257 { 258 return resources; 259 } 260 } 261 262 /** 263 * Get the set of projects taking into consideration either the 264 * overriding selection option or the base directory if no selection specificed. 265 * 266 * @param line the commandline 267 * @return the array of projects in build order 268 */ 269 private Resource[] getBaseSelection( CommandLine line ) throws Exception 270 { 271 String selection = (String) line.getValue( SELECT_OPTION, null ); 272 if( null != selection ) 273 { 274 getLogger().debug( "parsing selection: " + selection ); 275 Resource[] resources = m_library.select( selection, false, true ); 276 getLogger().debug( "selection: " + resources.length ); 277 return resources; 278 } 279 else 280 { 281 String work = System.getProperty( "user.dir" ); 282 getLogger().debug( "resolving selection in: " + work ); 283 File file = new File( work ).getCanonicalFile(); 284 Resource[] resources = m_library.select( file ); 285 return resources; 286 } 287 } 288 289 /** 290 * Build the supplied set of projects. If a build filure occurs then 291 * abort the build sequence and exit. 292 * 293 * @param line the commandline 294 * @param resources the sorted sequence of projects to build 295 */ 296 private boolean process( CommandLine line, Resource[] resources ) throws Exception 297 { 298 boolean validation = line.hasOption( VALIDATE_OPTION ); 299 if( validation ) 300 { 301 System.setProperty( "project.validation.enabled", "true" ); 302 } 303 URI uri = (URI) line.getValue( BUILDER_URI_OPTION, ANT_BUILDER_URI ); 304 if( resources.length > 1 ) 305 { 306 StringBuffer buffer = 307 new StringBuffer( "Initiating build sequence: (" + resources.length + ")\n" ); 308 for( int i=0; i<resources.length; i++ ) 309 { 310 Resource resource = resources[i]; 311 buffer.append( "\n (" + ( i+1 ) + ")\t" + resource.getResourcePath() ); 312 } 313 buffer.append( "\n" ); 314 getLogger().info( buffer.toString() ); 315 } 316 317 List list = line.getValues( TARGETS ); 318 String[] targets = (String[]) list.toArray( new String[ list.size() ] ); 319 for( int i=0; i<resources.length; i++ ) 320 { 321 Resource resource = resources[i]; 322 Part part = createPart( uri ); 323 Builder builder = createBuilder( part ); 324 boolean status = builder.build( resource, targets ); 325 if( !status ) 326 { 327 return status; 328 } 329 System.gc(); 330 } 331 return true; 332 } 333 334 /** 335 * Build the supplied set of projects. If a build filure occurs then 336 * abort the build sequence and exit. 337 * 338 * @param resources the sorted sequence of prouject to build 339 * @exception Exception if an error occurs 340 */ 341 private void list( Resource[] resources ) throws Exception 342 { 343 if( resources.length == 1 ) 344 { 345 Resource resource = resources[0]; 346 if( resource instanceof Module ) 347 { 348 listModule( (Module) resource ); 349 } 350 else 351 { 352 listResource( resource ); 353 } 354 } 355 else 356 { 357 listResources( resources ); 358 } 359 } 360 361 private void listModule( Module module ) throws Exception 362 { 363 print( "Listing module [" + module.getResourcePath() + "]\n" ); 364 listResource( " ", module, 0 ); 365 print( "" ); 366 } 367 368 private void listResource( Resource project ) throws Exception 369 { 370 print( "Listing project: " + project.getResourcePath() + "\n" ); 371 listResource( " ", project, 0 ); 372 print( "" ); 373 } 374 375 private void listResources( Resource[] resources ) throws Exception 376 { 377 print( "Selection: [" + resources.length + "]\n" ); 378 for( int i=0; i<resources.length; i++ ) 379 { 380 Resource resource = resources[i]; 381 String label = getLabel( i+1 ); 382 print( label + resource ); 383 } 384 print( "" ); 385 } 386 387 388 private void listResource( String pad, Resource resource, int n ) throws Exception 389 { 390 if( n > 0 ) 391 { 392 print( "\n[" + n + "] " + resource ); 393 } 394 else 395 { 396 print( "\n" + resource ); 397 } 398 print( "" ); 399 print( pad + "version: " + resource.getVersion() ); 400 print( pad + "basedir: " + resource.getBaseDir() ); 401 String p = pad + " "; 402 Type[] types = resource.getTypes(); 403 if( types.length > 0 ) 404 { 405 print( pad + "types: (" + types.length + ")" ); 406 for( int i=0; i<types.length; i++ ) 407 { 408 print( p + types[i].getID() ); 409 } 410 } 411 412 Resource[] resources = resource.getProviders( Scope.BUILD, m_expand, true ); 413 if( resources.length > 0 ) 414 { 415 print( pad + "build phase providers: (" + resources.length + ")" ); 416 for( int i=0; i<resources.length; i++ ) 417 { 418 Resource res = resources[i]; 419 print( p + res ); 420 } 421 } 422 resources = resource.getProviders( Scope.RUNTIME, m_expand, true ); 423 if( resources.length > 0 ) 424 { 425 print( pad + "runtime providers: (" + resources.length + ")" ); 426 for( int i=0; i<resources.length; i++ ) 427 { 428 Resource res = resources[i]; 429 print( p + res ); 430 } 431 } 432 resources = resource.getProviders( Scope.TEST, m_expand, true ); 433 if( resources.length > 0 ) 434 { 435 print( pad + "test providers: (" + resources.length + ")" ); 436 for( int i=0; i<resources.length; i++ ) 437 { 438 Resource res = resources[i]; 439 print( p + res ); 440 } 441 } 442 } 443 444 static void print( String message ) 445 { 446 System.out.println( message ); 447 } 448 449 private static String getLabel( int n ) 450 { 451 StringBuffer buffer = new StringBuffer(); 452 buffer.append( " [" + n ); 453 buffer.append( "]" ); 454 buffer.append( " " ); 455 String tag = buffer.toString(); 456 return tag.substring( 0, 7 ) + " "; 457 } 458 459 /** 460 * List general command help to the console. 461 * @exception IOException if an I/O error occurs 462 */ 463 private void processHelp() throws IOException 464 { 465 HelpFormatter formatter = new HelpFormatter( 466 HelpFormatter.DEFAULT_GUTTER_LEFT, 467 HelpFormatter.DEFAULT_GUTTER_CENTER, 468 HelpFormatter.DEFAULT_GUTTER_RIGHT, 469 100 ); 470 471 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_GROUP_OUTER ); 472 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 473 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 474 475 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL ); 476 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_GROUP_OUTER ); 477 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 478 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL ); 479 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 480 formatter.getFullUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN ); 481 482 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 483 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 484 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN ); 485 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_GROUP_EXPANDED ); 486 487 formatter.setGroup( COMMAND_GROUP ); 488 formatter.setShellCommand( "build" ); 489 formatter.print(); 490 } 491 492 private Logger getLogger() 493 { 494 return m_logger; 495 } 496 497 private static final DefaultOptionBuilder OPTION_BUILDER = new DefaultOptionBuilder(); 498 private static final ArgumentBuilder ARGUMENT_BUILDER = new ArgumentBuilder(); 499 private static final GroupBuilder GROUP_BUILDER = new GroupBuilder(); 500 private static final CommandBuilder COMMAND_BUILDER = new CommandBuilder(); 501 private static final PropertyOption PROPERTY_OPTION = new PropertyOption(); 502 503 private static final Option HELP_OPTION = 504 OPTION_BUILDER 505 .withShortName( "help" ) 506 .withShortName( "h" ) 507 .withDescription( "List command help." ) 508 .withRequired( false ) 509 .create(); 510 511 private static final Option SELECT_OPTION = 512 OPTION_BUILDER 513 .withShortName( "select" ) 514 .withShortName( "s" ) 515 .withDescription( "Build selected project(s)." ) 516 .withRequired( false ) 517 .withArgument( 518 ARGUMENT_BUILDER 519 .withDescription( "Project." ) 520 .withName( "pattern" ) 521 .withMinimum( 1 ) 522 .withMaximum( 1 ) 523 .create() ) 524 .create(); 525 526 private static final Option VERBOSE_OPTION = 527 OPTION_BUILDER 528 .withShortName( "verbose" ) 529 .withShortName( "v" ) 530 .withDescription( "Enable verbose mode." ) 531 .withRequired( false ) 532 .create(); 533 534 private static final Option LIST_OPTION = 535 OPTION_BUILDER 536 .withShortName( "list" ) 537 .withShortName( "l" ) 538 .withDescription( "List selected project(s)." ) 539 .withRequired( false ) 540 .withArgument( 541 ARGUMENT_BUILDER 542 .withDescription( "Project." ) 543 .withName( "pattern" ) 544 .withMinimum( 0 ) 545 .withMaximum( 1 ) 546 .create() ) 547 .create(); 548 549 private static final Option EXPAND_OPTION = 550 OPTION_BUILDER 551 .withShortName( "expand" ) 552 .withShortName( "e" ) 553 .withDescription( "Expand dependencies." ) 554 .withRequired( false ) 555 .create(); 556 557 private static final Option CONSUMERS_OPTION = 558 OPTION_BUILDER 559 .withShortName( "consumers" ) 560 .withShortName( "c" ) 561 .withDescription( "Consumer switch." ) 562 .withRequired( false ) 563 .create(); 564 565 private static final Option VALIDATE_OPTION = 566 OPTION_BUILDER 567 .withShortName( "validate" ) 568 .withDescription( "Enable deliverable validation." ) 569 .withRequired( false ) 570 .create(); 571 572 private static final Option VERSION_OPTION = 573 OPTION_BUILDER 574 .withShortName( "version" ) 575 .withDescription( "Build version." ) 576 .withRequired( false ) 577 .withArgument( 578 ARGUMENT_BUILDER 579 .withDescription( "Created artifact version." ) 580 .withName( "version" ) 581 .withMinimum( 1 ) 582 .withMaximum( 1 ) 583 .create() ) 584 .create(); 585 586 private static final Option DECIMAL_OPTION = 587 OPTION_BUILDER 588 .withShortName( "decimal" ) 589 .withDescription( "Enable decimal versioning." ) 590 .withRequired( false ) 591 .create(); 592 593 private static final Option BUILDER_URI_OPTION = 594 OPTION_BUILDER 595 .withShortName( "plugin" ) 596 .withDescription( "Default builder plugin uri." ) 597 .withRequired( false ) 598 .withArgument( 599 ARGUMENT_BUILDER 600 .withDescription( "Artifact reference." ) 601 .withName( "artifact" ) 602 .withMinimum( 1 ) 603 .withMaximum( 1 ) 604 .withValidator( new URIValidator() ) 605 .create() ) 606 .create(); 607 608 private static final Option TARGETS = 609 ARGUMENT_BUILDER 610 .withName( "target" ) 611 .create(); 612 613 private static final Group LIST_GROUP = 614 GROUP_BUILDER 615 .withMinimum( 0 ) 616 .withOption( LIST_OPTION ) 617 .withOption( EXPAND_OPTION ) 618 .withOption( CONSUMERS_OPTION ) 619 .create(); 620 621 private static final Group BUILD_GROUP = 622 GROUP_BUILDER 623 .withMinimum( 0 ) 624 .withOption( SELECT_OPTION ) 625 .withOption( CONSUMERS_OPTION ) 626 .withOption( VERBOSE_OPTION ) 627 .withOption( BUILDER_URI_OPTION ) 628 .withOption( VERSION_OPTION ) 629 .withOption( DECIMAL_OPTION ) 630 .withOption( VALIDATE_OPTION ) 631 .withOption( PROPERTY_OPTION ) 632 .create(); 633 634 private static final Group COMMAND_GROUP = 635 GROUP_BUILDER 636 .withName( "options" ) 637 .withOption( HELP_OPTION ) 638 .withOption( LIST_GROUP ) 639 .withOption( BUILD_GROUP ) 640 .withOption( TARGETS ) 641 .create(); 642 643 static 644 { 645 try 646 { 647 ANT_BUILDER_URI = new URI( "link:part:dpml/depot/dpml-tools-builder" ); 648 } 649 catch( Exception e ) 650 { 651 throw new RuntimeException( "will not happen", e ); 652 } 653 } 654 655 } 656